package edu.whu.notepal.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.whu.notepal.common.CustomException;
import edu.whu.notepal.dto.SocketBean;
import edu.whu.notepal.service.PrivateChatService;
import edu.whu.notepal.service.SubscriberService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


/**
 * @author 陈磊
 */
@ServerEndpoint(value = "/websocket/{id}")
@Component
@Slf4j
public class MyWebSocket {

    private static ConcurrentMap<Long,Session> map = new ConcurrentHashMap<>();

    private StringRedisTemplate stringRedisTemplate;

    private SubscriberService subscriberService;

    private static ApplicationContext applicationContext;

    private PrivateChatService privateChatService;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        MyWebSocket.applicationContext = applicationContext;
    }

    //转换器
    ObjectMapper mapper = new ObjectMapper();

    @SneakyThrows
    @OnOpen
    public void onOpen(Session session, @PathParam("id")Long id){
        stringRedisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        subscriberService = applicationContext.getBean(SubscriberService.class);
        privateChatService = applicationContext.getBean(PrivateChatService.class);
        log.info("new connection...current online count: {}",id);
        map.put(id,session);
        fetchMessage(id);
    }

    @OnClose
    public void onClose(@PathParam("id")Long id) {
        map.remove(id);
        log.info("one connection closed...current online count: {}",id);
    }

    @SneakyThrows
    @OnMessage
    public void onMessage(String message, Session session,@PathParam("id") Long id){
        log.info("message received: {}", message);
        SocketBean socketBean = mapper.readValue(message, SocketBean.class);
        socketBean.setFromUser(id);
        socketBean.setDateTime(LocalDateTime.now());
        log.info(socketBean.toString());
        switch (socketBean.getMessageType()){
            case FRIEND_REQUEST:
                sendFriendRequest(socketBean);
                break;
            case FRIEND_MESSAGE:
                sendFriendsMessage(socketBean);
                break;
            case GROUP_MESSAGE:
                sendGroupMessage(socketBean);
                break;
            case TEMPORARY_MESSAGE:
                sendTemporalMessage(socketBean);
                break;
            case FRIEND_ACCESS:
                sendFriendSuccess(socketBean);
                break;
            case FRIEND_DELETE:
                sendFriendDelete(socketBean);
                break;
            case PRIVATE_CHAT_ESTABLISHED:
                sendPrivateChateEstablished(socketBean);
                break;
            case PRIVATE_CHAT_CLOSED:
                sendPrivateChateClosed(socketBean);
                break;
            case GAME_REQUEST:
                sendGameRequest(socketBean);
                break;
            case GAME_REFUSED:
                sendGameRefused(socketBean);
                break;
            case GAME_EXIT:
                sendGameExit(socketBean);
                break;
            case GAME_START:
                sendGameStart(socketBean);
                break;
            case GAME_ONGOING:
                sendGameOngoing(socketBean);
                break;
            case GAME_TOPIC:
                sendGameTopic(socketBean);
                break;
            case GAME_IMG:
                sendGameMsg(socketBean);
                break;
            case GAME_REFRESH:
                sendRefreshMsg(socketBean);
                break;
            case CHAT_PIC:
                sendChatMsg(socketBean);
                break;
            case VIDEO_REQ:
                sendVideoRequest(socketBean);
                break;
            case VIDEO_CONFIRM:
                sendVideoConfirm(socketBean);
                break;
            case VIDEO_REFUSE:
                sendVideoRefused(socketBean);
                break;
            case VIDEO_OFFER_SEND:
                sendVideoOffer(socketBean);
                break;
            case VIDEO_ANSWER_SEND:
                sendVideoAnswer(socketBean);
                break;
            case VIDEO_ACANDIDATE:
                sendCandidate(socketBean);
                break;
            case VIDEO_BCANDIDATE:
                sendCandidate(socketBean);
                break;
            case VIDEO_CANCEL:
                sendVideoCancel(socketBean);
                break;
            default:
                throw new CustomException("未定义的消息类型!");
        }
        session.getAsyncRemote().sendText("发送消息成功！");
    }

    private void sendRefreshMsg(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendGameMsg(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendChatMsg(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendVideoOffer(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendVideoCancel(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendCandidate(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendVideoAnswer(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendGameTopic(SocketBean socketBean) {
        switch (socketBean.getGameCategory()){
            case 0:
                socketBean.setMsg(stringRedisTemplate.opsForSet().randomMember(RedisConstants.GAME_GUESS));
                sendToOne(socketBean);
                break;
            case 1:
                socketBean.setMsg(stringRedisTemplate.opsForSet().randomMember(RedisConstants.GAME_TRUEWORDS));
                sendToOne(socketBean);
                SocketBean.reverseUser(socketBean);
                sendToOne(socketBean);
                break;
            case 2:
                break;
            case 3:
                socketBean.setMsg(stringRedisTemplate.opsForSet().randomMember(RedisConstants.GAME_STORY));
                sendToOne(socketBean);
                SocketBean.reverseUser(socketBean);
                sendToOne(socketBean);
                break;
            default:
                throw new RuntimeException("游戏类型未定义！");
        }
    }

    private void sendGameOngoing(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendGameRequest(SocketBean socketBean) {
        sendToOne(socketBean);
    }
    private void sendVideoRequest(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendGameStart(SocketBean socketBean) {
        socketBean.setMsg("对方已接受游戏，游戏开始");
        sendToOne(socketBean);
        socketBean.setMessageType(MessageType.GAME_TOPIC);
        sendGameTopic(socketBean);
    }

    private void sendVideoConfirm(SocketBean socketBean) {
        socketBean.setMsg("对方已接受视频聊天");
        sendToOne(socketBean);
        socketBean.setMessageType(MessageType.VIDEO_CONFIRM);
        sendGameTopic(socketBean);
    }

    private void sendGameExit(SocketBean socketBean) {
        socketBean.setMsg("对方已退出游戏，游戏结束");
        sendToOne(socketBean);
    }

    private void sendGameRefused(SocketBean socketBean) {
        socketBean.setMsg("对方拒绝了你的游戏申请");
        sendToOne(socketBean);
    }

    private void sendVideoRefused(SocketBean socketBean) {
        socketBean.setMsg("对方拒绝了你的视频申请");
        sendToOne(socketBean);
    }


    @SneakyThrows
    public void sendToOneAbsent(Long id,SocketBean socketBean){
        stringRedisTemplate.opsForList().rightPush(RedisConstants.CHAT_MSG_BOX+id,mapper.writeValueAsString(socketBean));
        log.info("消息未接收:id={},socketBean={}",id,socketBean);
    }

    @SneakyThrows
    public void sendToOne(SocketBean socketBean){
        Long toUser = socketBean.getToUser();
        if(isOnline(toUser)){
            Session sessionToUser = getSessionById(toUser);
            log.info("id={},session={}",toUser,sessionToUser);
            sessionToUser.getAsyncRemote().sendText(mapper.writeValueAsString(socketBean));
        }else {
            sendToOneAbsent(toUser,socketBean);
        }
    }


    public boolean isOnline(Long id){
        return map.containsKey(id);
    }

    @SneakyThrows
    public Session getSessionById(Long id){
        return map.get(id);
    }

    public void sendFriendRequest(SocketBean socketBean){
        sendToOne(socketBean);
    }

    public void sendFriendsMessage(SocketBean socketBean){
        sendToOne(socketBean);
    }

    public void sendGroupMessage(SocketBean socketBean){
        log.info("发送群消息,socketBean={}",socketBean);
        Long groupId = socketBean.getToGroup();
        Long fromUser = socketBean.getFromUser();
        socketBean.setFromGroup(groupId);
        socketBean.setToGroup(null);
        List<Long> subscribersId = subscriberService.getSubscribersId(groupId);
        subscribersId.forEach(
                id->{
                    if(!id.equals(fromUser)){
                        socketBean.setToUser(id);
                        sendToOne(socketBean);
                    }
                }
        );
    }

    public void sendTemporalMessage(SocketBean socketBean){
        socketBean.setToUser(privateChatService.getTheOther(socketBean.getNoteId(),socketBean.getFromUser()));
        sendToOne(socketBean);
    }

    public void sendFriendSuccess(SocketBean socketBean){
        sendToOne(socketBean);
    }

    private void sendPrivateChateClosed(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendPrivateChateEstablished(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    private void sendFriendDelete(SocketBean socketBean) {
        sendToOne(socketBean);
    }

    public void fetchMessage(Long id){
        log.info("id={},取消息",id);
        Session session = getSessionById(id);
        String key = RedisConstants.CHAT_MSG_BOX + id;
        List<String> messages = stringRedisTemplate.opsForList().range( key,0, -1);
        messages.forEach( message->{
            SocketBean socketBean = null;
            try {
                socketBean = mapper.readValue(message, SocketBean.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            try {
                session.getAsyncRemote().sendText(mapper.writeValueAsString(socketBean));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        });
        stringRedisTemplate.delete(key);
    }
}